/**
 * This file is part of decent-cpp.
 *
 * decent-cpp is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * decent-cpp is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with decent-cpp.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "http.h"
#include <nlohmann/json.hpp>
#include <thread>
#include "data.h"
#include "util.h"
#include "ws.h"

using std::shared_ptr;
#ifdef USE_HTTPS
using HttpMode = SimpleWeb::HTTPS;
#else
using HttpMode = SimpleWeb::HTTP;
#endif
using HttpServer = SimpleWeb::Server<HttpMode>;
using HttpStatus = SimpleWeb::StatusCode;
using HttpHeaders = SimpleWeb::CaseInsensitiveMultimap;
using nlohmann::json;
using json_type = nlohmann::json::value_t;

namespace dc {
auto io_service = std::make_shared<SimpleWeb::asio::io_service>();
#ifdef USE_HTTPS
HttpServer server(HTTPS_CERT, HTTPS_KEY);
#else
HttpServer server;
#endif
void server_thread();

HttpHeaders server_common_headers() {
  HttpHeaders headers;
  headers.emplace("Content-Type", "application/json");
  headers.emplace("Access-Control-Allow-Origin", "*");
  headers.emplace("X-Frame-Options", "DENY");
  headers.emplace("X-Served-By", DECENT_CPP_VERSION);
  return headers;
}

typedef shared_ptr<HttpServer::Response> response_t;
typedef shared_ptr<HttpServer::Request> request_t;
typedef nonstd::optional<Session> session_t;
typedef std::function<void(request_t, response_t, HttpHeaders, session_t)>
    server_func_t;

void server_response_error(response_t response, HttpStatus status,
                           HttpHeaders headers, Error error,
                           std::string message) {
  json resp;
  resp["error"]["code"] = error_to_string(error);
  resp["error"]["message"] = message;
  response->write(status, resp.dump(), headers);
}

nonstd::optional<nlohmann::json> getJsonBody(request_t& request) {
  try {
    return nlohmann::json::parse(request->content.string());
  } catch (std::exception& ex) {
    return nonstd::nullopt;
  }
}

nonstd::optional<std::string> getJsonString(json& json, std::string key) {
  auto item = json.find(key);
  if (item == json.end()) {
    return nonstd::nullopt;
  }
  if (item->type() != json_type::string) {
    return nonstd::nullopt;
  }
  std::string str = *item;
  return str;
}

nonstd::optional<json> getJsonObject(json& json, std::string key) {
  auto item = json.find(key);
  if (item == json.end()) {
    return nonstd::nullopt;
  }
  if (item->type() != json_type::object) {
    return nonstd::nullopt;
  }
  nlohmann::json obj = *item;
  return obj;
}

/// Parses permissions. Handles printing errors. Pass userPerms to validate
/// against those perms
nonstd::optional<permissions_t> parsePermissions(
    response_t response, HttpHeaders headers, json& perms,
    nonstd::optional<permissions_t> userPerms) {
  permissions_t toSet;

  for (json::iterator it = perms.begin(); it != perms.end(); ++it) {
    auto perm = permissionFromString(it.key());
    if (!perm.has_value()) {
      server_response_error(response, HttpStatus::client_error_bad_request,
                            headers, Error::INCOMPLETE_PARAMETERS,
                            "Unrecognized permission");
      return nonstd::nullopt;
    }
    if (userPerms.has_value()) {
      if (permissionsGet(*userPerms, *perm) != DC_PERM_YES) {
        server_response_error(response, HttpStatus::client_error_forbidden,
                              headers, Error::NOT_ALLOWED,
                              "Cannot give permission you don't have");
        return nonstd::nullopt;
      }
    }
    auto value = it.value();
    if (value.type() != json_type::boolean) {
      server_response_error(response, HttpStatus::client_error_bad_request,
                            headers, Error::INVALID_PARAMETER_TYPE,
                            "Invalid parameter type");
      return nonstd::nullopt;
    }
    bool permValue = value;
    permissionsSet(toSet, *perm, permValue ? DC_PERM_YES : DC_PERM_NO);
  }
  return toSet;
}

void server_http_middleware(std::string const& epName, response_t response,
                            request_t request, server_func_t const& func) {
  HttpHeaders headers = server_common_headers();
  try {
    nonstd::optional<std::string> sessionStr(nonstd::nullopt);
    nonstd::optional<Session> session(nonstd::nullopt);
    auto query = request->parse_query_string();
    auto sess = query.find("sessionID");
    if (sess != query.end()) {
      sessionStr = sess->second;
    } else {
      auto& reqhdr = request->header;
      auto sess = reqhdr.find("X-Session-ID");
      if (sess != reqhdr.end()) {
        sessionStr = sess->second;
      }
    }
    if (sessionStr.has_value()) {
      if (sessionStr->size() != 32) {
        server_response_error(response, HttpStatus::client_error_bad_request,
                              headers, Error::INVALID_SESSION_ID,
                              "A session ID was provided but it was malformed");
        return;
      }
      auto mbid = string_to_uuid(*sessionStr);
      if (mbid.has_value()) {
        session = Session::getSession(*mbid);
      }
    }
    func(request, response, headers, session);
    return;
  } catch (std::runtime_error& ex) {
    printf("Exception in endpoint %s: %s\n", epName.c_str(), ex.what());
  } catch (std::exception& ex) {
    printf("Exception in endpoint %s: %s\n", epName.c_str(), ex.what());
  } catch (...) {
    printf("Exception in endpoint %s: unknown\n", epName.c_str());
  }
  server_response_error(response,
                        HttpStatus::server_error_internal_server_error, headers,
                        Error::FAILED, "Generic server error occurred");
}

void server_declare_ep(std::string method, std::string pattern,
                       server_func_t const& func) {
  server.resource[pattern][method] = [pattern, func](response_t response,
                                                     request_t request) {
    server_http_middleware(pattern, response, request, func);
  };
}

void server_http_init() {
  printf("Http server starting\n");
  server.io_service = io_service;
  server.config.port = DECENT_PORT;

  server_declare_ep(
      "GET", "^/api/?$",
      [](request_t, response_t response, HttpHeaders headers, session_t) {
        json resp;
        resp["decentVersion"] = version_api();
        resp["implementation"] = version_impl();
        resp["useSecureProtocol"] = false;
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "GET", "^/api/settings/?$",
      [](request_t, response_t response, HttpHeaders headers, session_t) {
        json resp;
        resp["settings"] = dc::data_setting_all();
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "PATCH", "^/api/settings/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        if (!session.has_value()) {
          server_response_error(response, HttpStatus::client_error_unauthorized,
                                headers, Error::NOT_ALLOWED, "Missing session");
          return;
        }
        User user = session->getUser();
        if (permissionsGet(user.effectivePermissions(nonstd::nullopt),
                           Permission::manageServer) != DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_unauthorized,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions for this action");
          return;
        }

        auto req = getJsonBody(request);
        if (!req.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Invalid request body");
          return;
        }
        json resp;
        for (json::iterator it = req->begin(); it != req->end(); ++it) {
          try {
            dc::data_setting_set(it.key(), it.value());
            resp["result"][it.key()] = "updated";
          } catch (std::runtime_error& ex) {
            printf("Ran into exception updating settings: %s\n", ex.what());
            resp["result"][it.key()] = "error";
          } catch (...) {
            printf("Ran into unknown exception updating settings\n");
            resp["result"][it.key()] = "error";
          }
        }
        response->write(HttpStatus::success_ok, resp.dump(), headers);

        json ws;
        ws["settings"] = dc::data_setting_all();
        server_ws_broadcast(Event::serversettings_update, ws);
      });

  server_declare_ep(
      "GET", "^/api/users/?$",
      [](request_t, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }

        json resp;

        std::vector<User> users = dc::User::getAllUsers();
        int i = 0;
        for (auto const& u : users) {
          resp["users"][i++] =
              u.to_json(user.has_value() && user->getId() == u.getId());
        }
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "GET", "^/api/users/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }

        std::string idStr = request->path_match[1];
        int userId;
        try {
          userId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Bad user id given");
          return;
        }

        auto u = User::getUser(userId);
        if (!u.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::NOT_FOUND, "No such user");
          return;
        }

        json resp;

        resp["user"] =
            u->to_json(user.has_value() && user->getId() == u->getId());

        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "POST", "^/api/users/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t) {
        auto req = getJsonBody(request);
        if (!req.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Bad request body");
          return;
        }
        auto username = getJsonString(*req, "username");
        auto password = getJsonString(*req, "password");
        if (!username.has_value() || !password.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Missing username or password");
          return;
        }
        if (password->size() < 6) {
          server_response_error(response, HttpStatus::client_error_conflict,
                                headers, Error::SHORT_PASSWORD,
                                "Password must be at least 6 chars");
          return;
        }
        auto user = User::registerUser(*username, *password);
        if (!user.has_value()) {
          server_response_error(response, HttpStatus::client_error_conflict,
                                headers, Error::NAME_ALREADY_TAKEN,
                                "Couldn't register user. Check name.");
          return;
        }
        json resp;
        resp["user"] = user->to_json(true);
        response->write(HttpStatus::success_ok, resp.dump(), headers);

        json ws;
        ws["user"] = user->to_json(false);
        dc::server_ws_broadcast(Event::user_new, ws);
      });
  
  server_declare_ep(
  "GET", "^^/api/users/([^/]+)/mentions?$",
  [](request_t request, response_t response, HttpHeaders headers,
     session_t session) {
    nonstd::optional<User> user;
    if (session.has_value()) {
      user = session->getUser();
    }
    
    std::string idStr = request->path_match[1];
    int userId;
    try {
      userId = std::stoi(idStr);
    } catch (...) {
      server_response_error(response, HttpStatus::client_error_bad_request,
                            headers, Error::INCOMPLETE_PARAMETERS,
                            "Bad user id given");
      return;
    }
    
    auto u = User::getUser(userId);
    if (!u.has_value()) {
      server_response_error(response, HttpStatus::client_error_not_found,
                            headers, Error::NOT_FOUND, "No such user");
      return;
    }
    
    auto query = request->parse_query_string();
    int limit = 50;
    int skip = 0;
    auto it = query.find("limit");
    if (it != query.end()) {
      try {
        limit = std::stoi(it->second);
      } catch(...) {}
    }
    it = query.find("skip");
    if (it != query.end()) {
      try {
        skip = std::stoi(it->second);
      } catch(...) {}
    }
    
    auto messages = Message::getMentionsForUser(*u, limit, skip, user);
    json resp;
    resp["mentions"] = json::array();
    int i = 0;
    for (auto& message: messages) {
      resp["mentions"][i++] = message.to_json();
    }
    response->write(HttpStatus::success_ok, resp.dump(), headers);
  });

  server_declare_ep(
      "GET", "^/api/sessions/?$",
      [](request_t, response_t response, HttpHeaders headers,
         session_t session) {
        if (session.has_value()) {
          std::vector<Session> all = session->getUserSessions();
          json resp;
          int i = 0;
          for (auto& s : all) {
            resp["sessions"][i++] = s.to_json();
          }
          response->write(HttpStatus::success_ok, resp.dump(), headers);
        } else {
          server_response_error(response, HttpStatus::client_error_unauthorized,
                                headers, Error::INVALID_SESSION_ID,
                                "Session required");
        }
      });

  server_declare_ep(
      "POST", "^/api/sessions/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t) {
        auto req = getJsonBody(request);
        if (req.has_value()) {
          auto reqJson = *req;
          auto username = getJsonString(*req, "username");
          auto password = getJsonString(*req, "password");
          if (!username.has_value() || !password.has_value()) {
            server_response_error(
                response, HttpStatus::client_error_bad_request, headers,
                Error::INCOMPLETE_PARAMETERS,
                "Missing username or password, or wrong type (expect string)");
            return;
          }
          int user = dc::User::loginUser(*username, *password);
          if (user > -1) {
            Session sess = dc::Session::newSession(user);
            json resp;
            resp["sessionID"] = sess.to_json()["id"];
            response->write(HttpStatus::success_ok, resp.dump(), headers);
          } else {
            server_response_error(
                response, HttpStatus::client_error_unauthorized, headers,
                Error::INCORRECT_PASSWORD, "Wrong username or password");
          }
        } else {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Invalid request body");
        }
      });
  server_declare_ep("DELETE", "^/api/sessions/([^/]+)/?$",
                    [](request_t request, response_t response,
                       HttpHeaders headers, session_t) {
                      std::string sessionHex = request->path_match[1];
                      auto id = string_to_uuid(sessionHex);
                      if (id.has_value()) {
                        auto sess = Session::getSession(*id);
                        if (sess.has_value()) {
                          sess->delet();
                        }
                      }
                      response->write(HttpStatus::success_ok, "{}", headers);
                    });
  server_declare_ep(
      "GET", "^/api/sessions/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t) {
        std::string sessionHex = request->path_match[1];
        auto id = string_to_uuid(sessionHex);
        if (id.has_value()) {
          auto session = Session::getSession(*id);
          if (session.has_value()) {
            User user = session->getUser();
            json resp;
            resp["session"] = session->to_json();
            resp["user"] = user.to_json(true);
            response->write(HttpStatus::success_ok, resp.dump(), headers);
            return;
          }
        }
        server_response_error(response, HttpStatus::client_error_unauthorized,
                              headers, Error::INVALID_SESSION_ID,
                              "Invalid session");

      });

  server_declare_ep(
      "GET", "^/api/roles/?$",
      [](request_t, response_t response, HttpHeaders headers, session_t) {
        std::vector<Role> allRoles = Role::getAllRoles();
        json resp;
        int i = 0;
        for (Role& role : allRoles) {
          resp["roles"][i++] = role.to_json();
        }
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "POST", "^/api/roles/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        if (!session.has_value()) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::INVALID_SESSION_ID,
                                "Session required");
          return;
        }
        User user = session->getUser();
        permissions_t userPerms = user.effectivePermissions(nonstd::nullopt);
        if (permissionsGet(userPerms, Permission::manageRoles) != DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_unauthorized,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions for this action");
          return;
        }
        auto roleReq = getJsonBody(request);
        if (!roleReq.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Unable to read request body");
          return;
        }
        auto name = getJsonString(*roleReq, "name");
        auto perms = getJsonObject(*roleReq, "permissions");
        if (!name.has_value() || !perms.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Missing parameters or invalid type");
          return;
        }

        auto toSet = parsePermissions(response, headers, *perms, userPerms);
        if (!toSet.has_value()) {
          return;
        }

        auto role = Role::newRole(*name, *toSet, false);
        if (!role.has_value()) {
          server_response_error(
              response, HttpStatus::client_error_conflict, headers,
              Error::FAILED,
              "Failed to create role. Check that it has a unique name");
          return;
        }

        json resp;
        resp["roleID"] = std::to_string(role->getId());
        response->write(HttpStatus::success_ok, resp.dump(), headers);

        json ws;
        ws["role"] = role->to_json();
        server_ws_broadcast(Event::role_new, ws);
      });

  server_declare_ep(
      "PATCH", "^/api/roles/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        if (!session.has_value()) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::INVALID_SESSION_ID,
                                "Session required");
          return;
        }
        User user = session->getUser();
        permissions_t userPerms = user.effectivePermissions(nonstd::nullopt);
        if (permissionsGet(userPerms, Permission::manageRoles) != DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_unauthorized,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions for this action");
          return;
        }

        std::string roleIdStr = request->path_match[1];

        auto role = Role::getRole(roleIdStr);
        if (!role.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::FAILED, "No such role");
          return;
        }

        auto roleReq = getJsonBody(request);
        if (!roleReq.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Unable to read request body");
          return;
        }

        auto name = getJsonString(*roleReq, "name");
        auto permsJson = getJsonObject(*roleReq, "permissions");
        nonstd::optional<permissions_t> perms;
        if (permsJson.has_value()) {
          perms = parsePermissions(response, headers, *permsJson, userPerms);
          if (!perms.has_value()) {
            return;
          }
        }

        if (role->edit(name, perms, nonstd::nullopt)) {
          response->write(HttpStatus::success_ok, "{}", headers);

          json ws;
          ws["role"] = role->to_json();
          server_ws_broadcast(Event::role_update, ws);
        } else {
          server_response_error(response, HttpStatus::client_error_conflict,
                                headers, Error::FAILED,
                                "Couldn't update role. Check for unique name");
        }
      });

  server_declare_ep(
      "DELETE", "^/api/roles/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        if (!session.has_value()) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::INVALID_SESSION_ID,
                                "Session required");
          return;
        }
        User user = session->getUser();
        permissions_t userPerms = user.effectivePermissions(nonstd::nullopt);
        if (permissionsGet(userPerms, Permission::manageRoles) != DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_unauthorized,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions for this action");
          return;
        }

        std::string roleIdStr = request->path_match[1];

        auto role = Role::getRole(roleIdStr);
        if (!role.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::FAILED, "No such role");
          return;
        }

        role->delet();

        response->write(HttpStatus::success_ok, "{}", headers);

        json ws;
        ws["roleID"] = roleIdStr;
        server_ws_broadcast(Event::role_delete, ws);
      });

  server_declare_ep(
      "GET", "^/api/channels/?$",
      [](request_t, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::vector<Channel> channels = Channel::getAllChannels();
        json resp;
        resp["channels"] = json::array();
        int i = 0;
        for (Channel& channel : channels) {
          permissions_t perms;
          if (user.has_value()) {
            perms = user->effectivePermissions(channel);
          } else {
            perms = Role::effectiveGuestPermissions(channel);
          }
          if (permissionsGet(perms, Permission::readMessages) == DC_PERM_YES) {
            resp["channels"][i++] = channel.to_json(user);
          }
        }
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "POST", "^/api/channels/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        permissions_t effective;
        if (session.has_value()) {
          effective = session->getUser().effectivePermissions(nonstd::nullopt);
        } else {
          effective = Role::effectiveGuestPermissions(nonstd::nullopt);
        }
        if (permissionsGet(effective, Permission::manageChannels) !=
            DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::NOT_ALLOWED,
                                "Need manageChannels to do this");
          return;
        }
        auto req = getJsonBody(request);
        if (!req.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Unable to read request body");
          return;
        }
        auto name = getJsonString(*req, "name");
        if (!name.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Channel name missing");
          return;
        }
        int channelId = Channel::newChannel(*name);
        if (channelId < 0) {
          server_response_error(
              response, HttpStatus::client_error_conflict, headers,
              Error::NAME_ALREADY_TAKEN,
              "Unable to create channel, check for unique name");
          return;
        }
        json resp;
        resp["channelID"] = std::to_string(channelId);
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "GET", "^/api/channels/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::string idStr = request->path_match[1];
        int channelId;
        try {
          channelId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid channel id");
          return;
        }
        auto channel = Channel::getChannel(channelId);
        if (!channel.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::INVALID_NAME,
                                "No such channel");
          return;
        }
        json resp;
        permissions_t perms;
        if (user.has_value()) {
          perms = user->effectivePermissions(channel);
        } else {
          perms = Role::effectiveGuestPermissions(channel);
        }
        if (permissionsGet(perms, Permission::readMessages) == DC_PERM_YES) {
          resp["channel"] = channel->to_json(user);
        } else {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions");
          return;
        }

        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "PATCH", "^/api/channels/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::string idStr = request->path_match[1];
        int channelId;
        try {
          channelId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid channel id");
          return;
        }
        auto channel = Channel::getChannel(channelId);
        if (!channel.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::INVALID_NAME,
                                "No such channel");
          return;
        }
        auto req = getJsonBody(request);
        if (!req.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Invalid request body");
          return;
        }
        auto name = getJsonString(*req, "name");
        if (!name.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Missing name");
          return;
        }

        permissions_t perms;
        if (user.has_value()) {
          perms = user->effectivePermissions(channel);
        } else {
          perms = Role::effectiveGuestPermissions(channel);
        }
        if (permissionsGet(perms, Permission::manageChannels) == DC_PERM_YES) {
          if (channel->edit(*name)) {
            response->write(HttpStatus::success_ok, "{}", headers);
          } else {
            server_response_error(response, HttpStatus::client_error_conflict,
                                  headers, Error::NAME_ALREADY_TAKEN,
                                  "Couldn't edit channel, check name");
            return;
          }
        } else {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions");
          return;
        }
      });

  server_declare_ep(
      "DELETE", "^/api/channels/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::string idStr = request->path_match[1];
        int channelId;
        try {
          channelId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid channel id");
          return;
        }
        auto channel = Channel::getChannel(channelId);
        if (!channel.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::INVALID_NAME,
                                "No such channel");
          return;
        }

        permissions_t perms;
        if (user.has_value()) {
          perms = user->effectivePermissions(channel);
        } else {
          perms = Role::effectiveGuestPermissions(channel);
        }
        if (permissionsGet(perms, Permission::manageChannels) == DC_PERM_YES) {
          channel->delet();
          response->write(HttpStatus::success_ok, "{}", headers);
        } else {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions");
          return;
        }
      });

  server_declare_ep(
      "POST", "^/api/channels/([^/]+)/mark-read/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        if (!session.has_value()) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::INVALID_SESSION_ID,
                                "Need session");
          return;
        }
        User user = session->getUser();
        std::string idStr = request->path_match[1];
        int channelId;
        try {
          channelId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid channel id");
          return;
        }
        auto channel = Channel::getChannel(channelId);
        if (!channel.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::INVALID_NAME,
                                "No such channel");
          return;
        }
        channel->markRead(user);
        response->write(HttpStatus::success_ok, "{}", headers);
        json ws;
        ws["channel"] = channel->to_json(user);
        server_ws_send(Event::channel_update, ws, session->getUuid());
      });

  server_declare_ep(
      "PATCH", "^/api/channels/([^/]+)/role-permissions/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::string idStr = request->path_match[1];
        int channelId;
        try {
          channelId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid channel id");
          return;
        }
        auto channel = Channel::getChannel(channelId);
        if (!channel.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::INVALID_NAME,
                                "No such channel");
          return;
        }

        permissions_t perms;
        if (user.has_value()) {
          perms = user->effectivePermissions(channel);
        } else {
          perms = Role::effectiveGuestPermissions(channel);
        }
        if (permissionsGet(perms, Permission::manageRoles) != DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions");
          return;
        }

        auto req = getJsonBody(request);
        if (!req.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given permissions");
          return;
        }
        auto rp = getJsonObject(*req, "rolePermissions");
        if (!rp.has_value()) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given permissions");
          return;
        }
        std::unordered_map<int, permissions_t> newPerms;
        for (json::iterator it = rp->begin(); it != rp->end(); ++it) {
          std::string roleName = it.key();
          auto rolePerms =
              parsePermissions(response, headers, it.value(), perms);
          if (!rolePerms.has_value()) {
            return;
          }
          auto role = Role::getRole(roleName);
          if (!role.has_value()) {
            server_response_error(response, HttpStatus::client_error_not_found,
                                  headers, Error::NOT_FOUND, "No such role");
            return;
          }
          newPerms.emplace(role->getId(), *rolePerms);
        }
        channel->setRolePermissions(newPerms);
        response->write(HttpStatus::success_ok, "{}", headers);
      });

  server_declare_ep(
      "GET", "^/api/channels/([^/]+)/role-permissions/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::string idStr = request->path_match[1];
        int channelId;
        try {
          channelId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid channel id");
          return;
        }
        auto channel = Channel::getChannel(channelId);
        if (!channel.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::INVALID_NAME,
                                "No such channel");
          return;
        }
        json resp(json::object());
        resp["rolePermissions"] = json::object();
        permissions_t perms;
        if (user.has_value()) {
          perms = user->effectivePermissions(channel);
        } else {
          perms = Role::effectiveGuestPermissions(channel);
        }
        if (permissionsGet(perms, Permission::readMessages) == DC_PERM_YES) {
          std::unordered_map<int, permissions_t> rolePerms =
              channel->getRolePermissions();
          for (auto it = rolePerms.begin(); it != rolePerms.end(); ++it) {
            int roleId = it->first;
            std::string roleName = Role::getRole(roleId)->getName();
            resp["rolePermissions"][roleName] = permissionsToJson(it->second);
          }
        }

        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "GET", "^/api/channels/([^/]+)/messages/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::string idStr = request->path_match[1];
        int channelId;
        try {
          channelId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid channel id");
          return;
        }
        auto channel = Channel::getChannel(channelId);
        if (!channel.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::INVALID_NAME,
                                "No such channel");
          return;
        }

        permissions_t perms;
        if (user.has_value()) {
          perms = user->effectivePermissions(channel);
        } else {
          perms = Role::effectiveGuestPermissions(channel);
        }
        if (permissionsGet(perms, Permission::readMessages) != DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions");
          return;
        }

        auto query = request->parse_query_string();
        nonstd::optional<int> beforeId;
        auto beforeIt = query.find("before");
        if (beforeIt != query.end()) {
          try {
            beforeId = std::stoi(beforeIt->second);
          } catch (...) {
          }
        }
        nonstd::optional<int> afterId;
        auto afterIt = query.find("after");
        if (afterIt != query.end()) {
          try {
            afterId = std::stoi(afterIt->second);
          } catch (...) {
          }
        }
        int limit = 50;
        auto limitIt = query.find("limit");
        if (limitIt != query.end()) {
          try {
            limit = std::stoi(limitIt->second);
          } catch (...) {
          }
        }
        std::vector<Message> messages =
            Message::getMessagesInChannel(*channel, limit, beforeId, afterId);
        json resp;
        resp["messages"] = json::array();
        int i = 0;
        for (auto& m : messages) {
          resp["messages"][i++] = m.to_json();
        }
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server_declare_ep(
      "POST", "^/api/messages/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        auto req = getJsonBody(request);
        if (req.has_value()) {
          auto channelIdStr = getJsonString(*req, "channelID");
          auto text = getJsonString(*req, "text");
          auto type = getJsonString(*req, "type");
          if (channelIdStr.has_value() && text.has_value()) {
            if (!type.has_value()) {
              type = std::string("user");
            }
            auto msgType = Message::stringToType(*type);
            if (msgType.has_value()) {
              int channelId;
              try {
                channelId = std::stoi(*channelIdStr);
              } catch (...) {
                server_response_error(response,
                                      HttpStatus::client_error_bad_request,
                                      headers, Error::INCOMPLETE_PARAMETERS,
                                      "Not given a valid channel id");
                return;
              }
              auto channel = Channel::getChannel(channelId);
              if (!channel.has_value()) {
                server_response_error(
                    response, HttpStatus::client_error_not_found, headers,
                    Error::INVALID_NAME, "No such channel");
                return;
              }

              permissions_t perms;
              if (user.has_value()) {
                perms = user->effectivePermissions(channel);
              } else {
                perms = Role::effectiveGuestPermissions(channel);
              }
              if (permissionsGet(perms, Permission::sendMessages) !=
                      DC_PERM_YES ||
                  (*type == "system" &&
                   permissionsGet(perms, Permission::sendSystemMessages) !=
                       DC_PERM_YES)) {
                server_response_error(
                    response, HttpStatus::client_error_forbidden, headers,
                    Error::NOT_ALLOWED, "Missing permissions");
                return;
              }

              // TODO: handle guest
              auto msg = Message::newMessage(*channel, *msgType, *text, *user);
              if (msg.has_value()) {
                json resp;
                resp["messageID"] = std::to_string(msg->getId());
                response->write(HttpStatus::success_ok, resp.dump(), headers);

                json bcast;
                bcast["message"] = msg->to_json();
                server_ws_broadcast(Event::message_new, bcast);

                // Mentions
                auto mentions = msg->getMentionedUserIds();
                for (auto id : mentions) {
                  auto sessions = Session::getSessions(id);
                  for (auto session : sessions) {
                    json mention;
                    mention["message"] = msg->to_json();
                    server_ws_send(Event::user_mentions_add, mention,
                                   session.getUuid());
                  }
                }
              } else {
                server_response_error(
                    response, HttpStatus::client_error_conflict, headers,
                    Error::FAILED, "Failed to send message");
              }
              return;
            }
          }
        }
        server_response_error(response, HttpStatus::client_error_bad_request,
                              headers, Error::INCOMPLETE_PARAMETERS,
                              "Missing parameters");
      });

  server_declare_ep(
      "GET", "^/api/messages/([^/]+)/?$",
      [](request_t request, response_t response, HttpHeaders headers,
         session_t session) {
        nonstd::optional<User> user;
        if (session.has_value()) {
          user = session->getUser();
        }
        std::string idStr = request->path_match[1];
        int msgId;
        try {
          msgId = std::stoi(idStr);
        } catch (...) {
          server_response_error(response, HttpStatus::client_error_bad_request,
                                headers, Error::INCOMPLETE_PARAMETERS,
                                "Not given a valid message id");
          return;
        }

        auto message = Message::getMessage(msgId);
        if (!message.has_value()) {
          server_response_error(response, HttpStatus::client_error_not_found,
                                headers, Error::NOT_FOUND, "No such message");
          return;
        }

        Channel channel = message->getChannel();
        permissions_t perms;
        if (user.has_value()) {
          perms = user->effectivePermissions(channel);
        } else {
          perms = Role::effectiveGuestPermissions(channel);
        }
        if (permissionsGet(perms, Permission::readMessages) != DC_PERM_YES) {
          server_response_error(response, HttpStatus::client_error_forbidden,
                                headers, Error::NOT_ALLOWED,
                                "Missing permissions");
          return;
        }
        json resp;
        resp["message"] = message->to_json();
        response->write(HttpStatus::success_ok, resp.dump(), headers);
      });

  server.default_resource["GET"] = [](response_t response, request_t) {
    server_response_error(response, HttpStatus::client_error_not_found,
                          server_common_headers(), Error::NOT_FOUND,
                          "No such endpoint");
  };
  server.default_resource["HEAD"] = server.default_resource["GET"];
  server.default_resource["POST"] = server.default_resource["GET"];
  server.default_resource["PUT"] = server.default_resource["GET"];
  server.default_resource["PATCH"] = server.default_resource["GET"];
  server.default_resource["DELETE"] = server.default_resource["GET"];

  server.default_resource["OPTIONS"] = [](response_t response, request_t) {
    HttpHeaders headers;
    headers.emplace("Access-Control-Allow-Origin", "*");
    headers.emplace("X-Served-By", DECENT_CPP_VERSION);
    headers.emplace("Access-Control-Allow-Methods",
                    "GET,HEAD,PUT,PATCH,POST,DELETE");
    headers.emplace("Access-Control-Allow-Headers", "Content-Type, X-Session-ID");
    headers.emplace("X-Frame-Options", "DENY");
    response->write(HttpStatus::success_ok, headers);
  };

  // WebSocket upgrade handler
  server.on_upgrade = [](std::unique_ptr<HttpMode>& socket,
                         std::shared_ptr<HttpServer::Request> request) {
    printf("Got upgrade request\n");
    auto connection = std::make_shared<WsServer::Connection>(std::move(socket));
    connection->method = std::move(request->method);
    connection->path = std::move(request->path);
    connection->http_version = std::move(request->http_version);
    connection->header = std::move(request->header);
    connection->remote_endpoint = std::move(*request->remote_endpoint);
    server_ws_upgrade(connection);
  };

  server_ws_init();

  std::thread(server_thread).detach();
  printf("Http server started\n");
}

void server_http_kill() {
  printf("Http server stopping\n");
  server_ws_kill();
  server.stop();
}

std::shared_ptr<SimpleWeb::asio::io_service>& get_io_service() {
  return io_service;
}

void server_thread() {
  server.start();
  io_service->run();
}
}  // namespace dc